/***
 * ASM: a very small and fast Java bytecode manipulation framework
 * Copyright (c) 2000-2007 INRIA, France Telecom
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holders nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.rsbot.loader.asm;

import java.io.IOException;
import java.io.InputStream;

/**
 * A Java class parser to make a {@link ClassVisitor} visit an existing class.
 * This class parses a byte array conforming to the Java class file format and
 * calls the appropriate visit methods of a given class visitor for each field,
 * method and bytecode instruction encountered.
 * 
 * @author Eric Bruneton
 * @author Eugene Kuleshov
 */
public class ClassReader {

	/**
	 * True to enable signatures support.
	 */
	static final boolean SIGNATURES = true;

	/**
	 * True to enable annotations support.
	 */
	static final boolean ANNOTATIONS = true;

	/**
	 * True to enable stack map frames support.
	 */
	static final boolean FRAMES = true;

	/**
	 * True to enable bytecode writing support.
	 */
	static final boolean WRITER = true;

	/**
	 * True to enable JSR_W and GOTO_W support.
	 */
	static final boolean RESIZE = true;

	/**
	 * Flag to skip method code. If this class is set <code>CODE</code>
	 * attribute won't be visited. This can be used, for example, to retrieve
	 * annotations for methods and method parameters.
	 */
	public static final int SKIP_CODE = 1;

	/**
	 * Flag to skip the debug information in the class. If this flag is set the
	 * debug information of the class is not visited, i.e. the
	 * {@link MethodVisitor#visitLocalVariable visitLocalVariable} and
	 * {@link MethodVisitor#visitLineNumber visitLineNumber} methods will not be
	 * called.
	 */
	public static final int SKIP_DEBUG = 2;

	/**
	 * Flag to skip the stack map frames in the class. If this flag is set the
	 * stack map frames of the class is not visited, i.e. the
	 * {@link MethodVisitor#visitFrame visitFrame} method will not be called.
	 * This flag is useful when the {@link ClassWriter#COMPUTE_FRAMES} option is
	 * used: it avoids visiting frames that will be ignored and recomputed from
	 * scratch in the class writer.
	 */
	public static final int SKIP_FRAMES = 4;

	/**
	 * Flag to expand the stack map frames. By default stack map frames are
	 * visited in their original format (i.e. "expanded" for classes whose
	 * version is less than V1_6, and "compressed" for the other classes). If
	 * this flag is set, stack map frames are always visited in expanded format
	 * (this option adds a decompression/recompression step in ClassReader and
	 * ClassWriter which degrades performances quite a lot).
	 */
	public static final int EXPAND_FRAMES = 8;

	/**
	 * Reads the bytecode of a class.
	 * 
	 * @param is
	 *            an input stream from which to read the class.
	 * @return the bytecode read from the given input stream.
	 * @throws IOException
	 *             if a problem occurs during reading.
	 */
	private static byte[] readClass(final InputStream is) throws IOException {
		if (is == null) {
			throw new IOException("Class not found");
		}
		byte[] b = new byte[is.available()];
		int len = 0;
		while (true) {
			final int n = is.read(b, len, b.length - len);
			if (n == -1) {
				if (len < b.length) {
					final byte[] c = new byte[len];
					System.arraycopy(b, 0, c, 0, len);
					b = c;
				}
				return b;
			}
			len += n;
			if (len == b.length) {
				final int last = is.read();
				if (last < 0) {
					return b;
				}
				final byte[] c = new byte[b.length + 1000];
				System.arraycopy(b, 0, c, 0, len);
				c[len++] = (byte) last;
				b = c;
			}
		}
	}

	/**
	 * The class to be parsed. <i>The content of this array must not be
	 * modified. This field is intended for {@link Attribute} sub classes, and
	 * is normally not needed by class generators or adapters.</i>
	 */
	public final byte[] b;

	/**
	 * The start index of each constant pool item in {@link #b b}, plus one. The
	 * one byte offset skips the constant pool item tag that indicates its type.
	 */
	private final int[] items;

	/**
	 * The String objects corresponding to the CONSTANT_Utf8 items. This cache
	 * avoids multiple parsing of a given CONSTANT_Utf8 constant pool item,
	 * which GREATLY improves performances (by a factor 2 to 3). This caching
	 * strategy could be extended to all constant pool items, but its benefit
	 * would not be so great for these items (because they are much less
	 * expensive to parse than CONSTANT_Utf8 items).
	 */
	private final String[] strings;

	/**
	 * Maximum length of the strings contained in the constant pool of the
	 * class.
	 */
	private final int maxStringLength;

	// ------------------------------------------------------------------------
	// Constructors
	// ------------------------------------------------------------------------

	/**
	 * Start index of the class header information (access, name...) in
	 * {@link #b b}.
	 */
	public final int header;

	/**
	 * Constructs a new {@link ClassReader} object.
	 * 
	 * @param b
	 *            the bytecode of the class to be read.
	 */
	public ClassReader(final byte[] b) {
		this(b, 0, b.length);
	}

	/**
	 * Constructs a new {@link ClassReader} object.
	 * 
	 * @param b
	 *            the bytecode of the class to be read.
	 * @param off
	 *            the start offset of the class data.
	 * @param len
	 *            the length of the class data.
	 */
	public ClassReader(final byte[] b, final int off, final int len) {
		this.b = b;
		// parses the constant pool
		items = new int[readUnsignedShort(off + 8)];
		final int n = items.length;
		strings = new String[n];
		int max = 0;
		int index = off + 10;
		for (int i = 1; i < n; ++i) {
			items[i] = index + 1;
			int size;
			switch (b[index]) {
			case ClassWriter.FIELD:
			case ClassWriter.METH:
			case ClassWriter.IMETH:
			case ClassWriter.INT:
			case ClassWriter.FLOAT:
			case ClassWriter.NAME_TYPE:
				size = 5;
				break;
			case ClassWriter.LONG:
			case ClassWriter.DOUBLE:
				size = 9;
				++i;
				break;
			case ClassWriter.UTF8:
				size = 3 + readUnsignedShort(index + 1);
				if (size > max) {
					max = size;
				}
				break;
				// case ClassWriter.CLASS:
				// case ClassWriter.STR:
			default:
				size = 3;
				break;
			}
			index += size;
		}
		maxStringLength = max;
		// the class header information starts just after the constant pool
		header = index;
	}

	/**
	 * Constructs a new {@link ClassReader} object.
	 * 
	 * @param is
	 *            an input stream from which to read the class.
	 * @throws IOException
	 *             if a problem occurs during reading.
	 */
	public ClassReader(final InputStream is) throws IOException {
		this(readClass(is));
	}

	/**
	 * Constructs a new {@link ClassReader} object.
	 * 
	 * @param name
	 *            the fully qualified name of the class to be read.
	 * @throws IOException
	 *             if an exception occurs during reading.
	 */
	public ClassReader(final String name) throws IOException {
		this(ClassLoader.getSystemResourceAsStream(name.replace('.', '/')
				+ ".class"));
	}

	/**
	 * Makes the given visitor visit the Java class of this {@link ClassReader}.
	 * This class is the one specified in the constructor (see
	 * {@link #ClassReader(byte[]) ClassReader}).
	 * 
	 * @param classVisitor
	 *            the visitor that must visit this class.
	 * @param attrs
	 *            prototypes of the attributes that must be parsed during the
	 *            visit of the class. Any attribute whose type is not equal to
	 *            the type of one the prototypes will not be parsed: its byte
	 *            array value will be passed unchanged to the ClassWriter.
	 *            <i>This may corrupt it if this value contains references to
	 *            the constant pool, or has syntactic or semantic links with a
	 *            class element that has been transformed by a class adapter
	 *            between the reader and the writer</i>.
	 * @param flags
	 *            option flags that can be used to modify the default behavior
	 *            of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}
	 *            , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}.
	 */
	public void accept(final ClassVisitor classVisitor,
			final Attribute[] attrs, final int flags) {
		final byte[] b = this.b; // the bytecode array
		final char[] c = new char[maxStringLength]; // buffer used to read
		// strings
		int i, j, k; // loop variables
		int u, v, w; // indexes in b
		Attribute attr;

		int access;
		String name;
		String desc;
		String attrName;
		String signature;
		int anns = 0;
		int ianns = 0;
		Attribute cattrs = null;

		// visits the header
		u = header;
		access = readUnsignedShort(u);
		name = readClass(u + 2, c);
		v = items[readUnsignedShort(u + 4)];
		final String superClassName = v == 0 ? null : readUTF8(v, c);
		final String[] implementedItfs = new String[readUnsignedShort(u + 6)];
		w = 0;
		u += 8;
		for (i = 0; i < implementedItfs.length; ++i) {
			implementedItfs[i] = readClass(u, c);
			u += 2;
		}

		final boolean skipCode = (flags & SKIP_CODE) != 0;
		final boolean skipDebug = (flags & SKIP_DEBUG) != 0;
		final boolean unzip = (flags & EXPAND_FRAMES) != 0;

		// skips fields and methods
		v = u;
		i = readUnsignedShort(v);
		v += 2;
		for (; i > 0; --i) {
			j = readUnsignedShort(v + 6);
			v += 8;
			for (; j > 0; --j) {
				v += 6 + readInt(v + 2);
			}
		}
		i = readUnsignedShort(v);
		v += 2;
		for (; i > 0; --i) {
			j = readUnsignedShort(v + 6);
			v += 8;
			for (; j > 0; --j) {
				v += 6 + readInt(v + 2);
			}
		}
		// reads the class's attributes
		signature = null;
		String sourceFile = null;
		String sourceDebug = null;
		String enclosingOwner = null;
		String enclosingName = null;
		String enclosingDesc = null;

		i = readUnsignedShort(v);
		v += 2;
		for (; i > 0; --i) {
			attrName = readUTF8(v, c);
			// tests are sorted in decreasing frequency order
			// (based on frequencies observed on typical classes)
			if ("SourceFile".equals(attrName)) {
				sourceFile = readUTF8(v + 6, c);
			} else if ("InnerClasses".equals(attrName)) {
				w = v + 6;
			} else if ("EnclosingMethod".equals(attrName)) {
				enclosingOwner = readClass(v + 6, c);
				final int item = readUnsignedShort(v + 8);
				if (item != 0) {
					enclosingName = readUTF8(items[item], c);
					enclosingDesc = readUTF8(items[item] + 2, c);
				}
			} else if (SIGNATURES && "Signature".equals(attrName)) {
				signature = readUTF8(v + 6, c);
			} else if (ANNOTATIONS
					&& "RuntimeVisibleAnnotations".equals(attrName)) {
				anns = v + 6;
			} else if ("Deprecated".equals(attrName)) {
				access |= Opcodes.ACC_DEPRECATED;
			} else if ("Synthetic".equals(attrName)) {
				access |= Opcodes.ACC_SYNTHETIC
				| ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
			} else if ("SourceDebugExtension".equals(attrName)) {
				final int len = readInt(v + 2);
				sourceDebug = readUTF(v + 6, len, new char[len]);
			} else if (ANNOTATIONS
					&& "RuntimeInvisibleAnnotations".equals(attrName)) {
				ianns = v + 6;
			} else {
				attr = readAttribute(attrs, attrName, v + 6, readInt(v + 2), c,
						-1, null);
				if (attr != null) {
					attr.next = cattrs;
					cattrs = attr;
				}
			}
			v += 6 + readInt(v + 2);
		}
		// calls the visit method
		classVisitor.visit(readInt(4), access, name, signature, superClassName,
				implementedItfs);

		// calls the visitSource method
		if (!skipDebug && (sourceFile != null || sourceDebug != null)) {
			classVisitor.visitSource(sourceFile, sourceDebug);
		}

		// calls the visitOuterClass method
		if (enclosingOwner != null) {
			classVisitor.visitOuterClass(enclosingOwner, enclosingName,
					enclosingDesc);
		}

		// visits the class annotations
		if (ANNOTATIONS) {
			for (i = 1; i >= 0; --i) {
				v = i == 0 ? ianns : anns;
				if (v != 0) {
					j = readUnsignedShort(v);
					v += 2;
					for (; j > 0; --j) {
						v = readAnnotationValues(v + 2, c, true,
								classVisitor.visitAnnotation(readUTF8(v, c),
										i != 0));
					}
				}
			}
		}

		// visits the class attributes
		while (cattrs != null) {
			attr = cattrs.next;
			cattrs.next = null;
			classVisitor.visitAttribute(cattrs);
			cattrs = attr;
		}

		// calls the visitInnerClass method
		if (w != 0) {
			i = readUnsignedShort(w);
			w += 2;
			for (; i > 0; --i) {
				classVisitor.visitInnerClass(
						readUnsignedShort(w) == 0 ? null : readClass(w, c),
								readUnsignedShort(w + 2) == 0 ? null : readClass(w + 2,
										c),
										readUnsignedShort(w + 4) == 0 ? null : readUTF8(w + 4,
												c), readUnsignedShort(w + 6));
				w += 8;
			}
		}

		// visits the fields
		i = readUnsignedShort(u);
		u += 2;
		for (; i > 0; --i) {
			access = readUnsignedShort(u);
			name = readUTF8(u + 2, c);
			desc = readUTF8(u + 4, c);
			// visits the field's attributes and looks for a ConstantValue
			// attribute
			int fieldValueItem = 0;
			signature = null;
			anns = 0;
			ianns = 0;
			cattrs = null;

			j = readUnsignedShort(u + 6);
			u += 8;
			for (; j > 0; --j) {
				attrName = readUTF8(u, c);
				// tests are sorted in decreasing frequency order
				// (based on frequencies observed on typical classes)
				if ("ConstantValue".equals(attrName)) {
					fieldValueItem = readUnsignedShort(u + 6);
				} else if (SIGNATURES && "Signature".equals(attrName)) {
					signature = readUTF8(u + 6, c);
				} else if ("Deprecated".equals(attrName)) {
					access |= Opcodes.ACC_DEPRECATED;
				} else if ("Synthetic".equals(attrName)) {
					access |= Opcodes.ACC_SYNTHETIC
					| ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
				} else if (ANNOTATIONS
						&& "RuntimeVisibleAnnotations".equals(attrName)) {
					anns = u + 6;
				} else if (ANNOTATIONS
						&& "RuntimeInvisibleAnnotations".equals(attrName)) {
					ianns = u + 6;
				} else {
					attr = readAttribute(attrs, attrName, u + 6,
							readInt(u + 2), c, -1, null);
					if (attr != null) {
						attr.next = cattrs;
						cattrs = attr;
					}
				}
				u += 6 + readInt(u + 2);
			}
			// visits the field
			final FieldVisitor fv = classVisitor.visitField(access, name, desc,
					signature,
					fieldValueItem == 0 ? null : readConst(fieldValueItem, c));
			// visits the field annotations and attributes
			if (fv != null) {
				if (ANNOTATIONS) {
					for (j = 1; j >= 0; --j) {
						v = j == 0 ? ianns : anns;
						if (v != 0) {
							k = readUnsignedShort(v);
							v += 2;
							for (; k > 0; --k) {
								v = readAnnotationValues(v + 2, c, true,
										fv.visitAnnotation(readUTF8(v, c),
												j != 0));
							}
						}
					}
				}
				while (cattrs != null) {
					attr = cattrs.next;
					cattrs.next = null;
					fv.visitAttribute(cattrs);
					cattrs = attr;
				}
				fv.visitEnd();
			}
		}

		// visits the methods
		i = readUnsignedShort(u);
		u += 2;
		for (; i > 0; --i) {
			final int u0 = u + 6;
			access = readUnsignedShort(u);
			name = readUTF8(u + 2, c);
			desc = readUTF8(u + 4, c);
			signature = null;
			anns = 0;
			ianns = 0;
			int dann = 0;
			int mpanns = 0;
			int impanns = 0;
			cattrs = null;
			v = 0;
			w = 0;

			// looks for Code and Exceptions attributes
			j = readUnsignedShort(u + 6);
			u += 8;
			for (; j > 0; --j) {
				attrName = readUTF8(u, c);
				final int attrSize = readInt(u + 2);
				u += 6;
				// tests are sorted in decreasing frequency order
				// (based on frequencies observed on typical classes)
				if ("Code".equals(attrName)) {
					if (!skipCode) {
						v = u;
					}
				} else if ("Exceptions".equals(attrName)) {
					w = u;
				} else if (SIGNATURES && "Signature".equals(attrName)) {
					signature = readUTF8(u, c);
				} else if ("Deprecated".equals(attrName)) {
					access |= Opcodes.ACC_DEPRECATED;
				} else if (ANNOTATIONS
						&& "RuntimeVisibleAnnotations".equals(attrName)) {
					anns = u;
				} else if (ANNOTATIONS && "AnnotationDefault".equals(attrName)) {
					dann = u;
				} else if ("Synthetic".equals(attrName)) {
					access |= Opcodes.ACC_SYNTHETIC
					| ClassWriter.ACC_SYNTHETIC_ATTRIBUTE;
				} else if (ANNOTATIONS
						&& "RuntimeInvisibleAnnotations".equals(attrName)) {
					ianns = u;
				} else if (ANNOTATIONS
						&& "RuntimeVisibleParameterAnnotations"
						.equals(attrName)) {
					mpanns = u;
				} else if (ANNOTATIONS
						&& "RuntimeInvisibleParameterAnnotations"
						.equals(attrName)) {
					impanns = u;
				} else {
					attr = readAttribute(attrs, attrName, u, attrSize, c, -1,
							null);
					if (attr != null) {
						attr.next = cattrs;
						cattrs = attr;
					}
				}
				u += attrSize;
			}
			// reads declared exceptions
			String[] exceptions;
			if (w == 0) {
				exceptions = null;
			} else {
				exceptions = new String[readUnsignedShort(w)];
				w += 2;
				for (j = 0; j < exceptions.length; ++j) {
					exceptions[j] = readClass(w, c);
					w += 2;
				}
			}

			// visits the method's code, if any
			final MethodVisitor mv = classVisitor.visitMethod(access, name,
					desc, signature, exceptions);

			if (mv != null) {
				/*
				 * if the returned MethodVisitor is in fact a MethodWriter, it
				 * means there is no method adapter between the reader and the
				 * writer. If, in addition, the writer's constant pool was
				 * copied from this reader (mw.cw.cr == this), and the signature
				 * and exceptions of the method have not been changed, then it
				 * is possible to skip all visit events and just copy the
				 * original code of the method to the writer (the access, name
				 * and descriptor can have been changed, this is not important
				 * since they are not copied as is from the reader).
				 */
				if (WRITER && mv instanceof MethodWriter) {
					final MethodWriter mw = (MethodWriter) mv;
					if (mw.cw.cr == this) {
						if (signature == mw.signature) {
							boolean sameExceptions = false;
							if (exceptions == null) {
								sameExceptions = mw.exceptionCount == 0;
							} else {
								if (exceptions.length == mw.exceptionCount) {
									sameExceptions = true;
									for (j = exceptions.length - 1; j >= 0; --j) {
										w -= 2;
										if (mw.exceptions[j] != readUnsignedShort(w)) {
											sameExceptions = false;
											break;
										}
									}
								}
							}
							if (sameExceptions) {
								/*
								 * we do not copy directly the code into
								 * MethodWriter to save a byte array copy
								 * operation. The real copy will be done in
								 * ClassWriter.toByteArray().
								 */
								mw.classReaderOffset = u0;
								mw.classReaderLength = u - u0;
								continue;
							}
						}
					}
				}

				if (ANNOTATIONS && dann != 0) {
					final AnnotationVisitor dv = mv.visitAnnotationDefault();
					readAnnotationValue(dann, c, null, dv);
					if (dv != null) {
						dv.visitEnd();
					}
				}
				if (ANNOTATIONS) {
					for (j = 1; j >= 0; --j) {
						w = j == 0 ? ianns : anns;
						if (w != 0) {
							k = readUnsignedShort(w);
							w += 2;
							for (; k > 0; --k) {
								w = readAnnotationValues(w + 2, c, true,
										mv.visitAnnotation(readUTF8(w, c),
												j != 0));
							}
						}
					}
				}
				if (ANNOTATIONS && mpanns != 0) {
					readParameterAnnotations(mpanns, desc, c, true, mv);
				}
				if (ANNOTATIONS && impanns != 0) {
					readParameterAnnotations(impanns, desc, c, false, mv);
				}
				while (cattrs != null) {
					attr = cattrs.next;
					cattrs.next = null;
					mv.visitAttribute(cattrs);
					cattrs = attr;
				}
			}

			if (mv != null && v != 0) {
				final int maxStack = readUnsignedShort(v);
				final int maxLocals = readUnsignedShort(v + 2);
				final int codeLength = readInt(v + 4);
				v += 8;

				final int codeStart = v;
				final int codeEnd = v + codeLength;

				mv.visitCode();

				// 1st phase: finds the labels
				int label;
				final Label[] labels = new Label[codeLength + 2];
				readLabel(codeLength + 1, labels);
				while (v < codeEnd) {
					w = v - codeStart;
					int opcode = b[v] & 0xFF;
					switch (ClassWriter.TYPE[opcode]) {
					case ClassWriter.NOARG_INSN:
					case ClassWriter.IMPLVAR_INSN:
						v += 1;
						break;
					case ClassWriter.LABEL_INSN:
						readLabel(w + readShort(v + 1), labels);
						v += 3;
						break;
					case ClassWriter.LABELW_INSN:
						readLabel(w + readInt(v + 1), labels);
						v += 5;
						break;
					case ClassWriter.WIDE_INSN:
						opcode = b[v + 1] & 0xFF;
						if (opcode == Opcodes.IINC) {
							v += 6;
						} else {
							v += 4;
						}
						break;
					case ClassWriter.TABL_INSN:
						// skips 0 to 3 padding bytes*
						v = v + 4 - (w & 3);
						// reads instruction
						readLabel(w + readInt(v), labels);
						j = readInt(v + 8) - readInt(v + 4) + 1;
						v += 12;
						for (; j > 0; --j) {
							readLabel(w + readInt(v), labels);
							v += 4;
						}
						break;
					case ClassWriter.LOOK_INSN:
						// skips 0 to 3 padding bytes*
						v = v + 4 - (w & 3);
						// reads instruction
						readLabel(w + readInt(v), labels);
						j = readInt(v + 4);
						v += 8;
						for (; j > 0; --j) {
							readLabel(w + readInt(v + 4), labels);
							v += 8;
						}
						break;
					case ClassWriter.VAR_INSN:
					case ClassWriter.SBYTE_INSN:
					case ClassWriter.LDC_INSN:
						v += 2;
						break;
					case ClassWriter.SHORT_INSN:
					case ClassWriter.LDCW_INSN:
					case ClassWriter.FIELDORMETH_INSN:
					case ClassWriter.TYPE_INSN:
					case ClassWriter.IINC_INSN:
						v += 3;
						break;
					case ClassWriter.ITFDYNMETH_INSN:
						v += 5;
						break;
						// case MANA_INSN:
					default:
						v += 4;
						break;
					}
				}
				// parses the try catch entries
				j = readUnsignedShort(v);
				v += 2;
				for (; j > 0; --j) {
					final Label start = readLabel(readUnsignedShort(v), labels);
					final Label end = readLabel(readUnsignedShort(v + 2),
							labels);
					final Label handler = readLabel(readUnsignedShort(v + 4),
							labels);
					final int type = readUnsignedShort(v + 6);
					if (type == 0) {
						mv.visitTryCatchBlock(start, end, handler, null);
					} else {
						mv.visitTryCatchBlock(start, end, handler,
								readUTF8(items[type], c));
					}
					v += 8;
				}
				// parses the local variable, line number tables, and code
				// attributes
				int varTable = 0;
				int varTypeTable = 0;
				int stackMap = 0;
				int stackMapSize = 0;
				int frameCount = 0;
				int frameMode = 0;
				int frameOffset = 0;
				int frameLocalCount = 0;
				int frameLocalDiff = 0;
				int frameStackCount = 0;
				Object[] frameLocal = null;
				Object[] frameStack = null;
				boolean zip = true;
				cattrs = null;
				j = readUnsignedShort(v);
				v += 2;
				for (; j > 0; --j) {
					attrName = readUTF8(v, c);
					if ("LocalVariableTable".equals(attrName)) {
						if (!skipDebug) {
							varTable = v + 6;
							k = readUnsignedShort(v + 6);
							w = v + 8;
							for (; k > 0; --k) {
								label = readUnsignedShort(w);
								if (labels[label] == null) {
									readLabel(label, labels).status |= Label.DEBUG;
								}
								label += readUnsignedShort(w + 2);
								if (labels[label] == null) {
									readLabel(label, labels).status |= Label.DEBUG;
								}
								w += 10;
							}
						}
					} else if ("LocalVariableTypeTable".equals(attrName)) {
						varTypeTable = v + 6;
					} else if ("LineNumberTable".equals(attrName)) {
						if (!skipDebug) {
							k = readUnsignedShort(v + 6);
							w = v + 8;
							for (; k > 0; --k) {
								label = readUnsignedShort(w);
								if (labels[label] == null) {
									readLabel(label, labels).status |= Label.DEBUG;
								}
								labels[label].line = readUnsignedShort(w + 2);
								w += 4;
							}
						}
					} else if (FRAMES && "StackMapTable".equals(attrName)) {
						if ((flags & SKIP_FRAMES) == 0) {
							stackMap = v + 8;
							stackMapSize = readInt(v + 2);
							frameCount = readUnsignedShort(v + 6);
						}
						/*
						 * here we do not extract the labels corresponding to
						 * the attribute content. This would require a full
						 * parsing of the attribute, which would need to be
						 * repeated in the second phase (see below). Instead the
						 * content of the attribute is read one frame at a time
						 * (i.e. after a frame has been visited, the next frame
						 * is read), and the labels it contains are also
						 * extracted one frame at a time. Thanks to the ordering
						 * of frames, having only a "one frame lookahead" is not
						 * a problem, i.e. it is not possible to see an offset
						 * smaller than the offset of the current insn and for
						 * which no Label exist.
						 */
						/*
						 * This is not true for UNINITIALIZED type offsets. We
						 * solve this by parsing the stack map table without a
						 * full decoding (see below).
						 */
					} else if (FRAMES && "StackMap".equals(attrName)) {
						if ((flags & SKIP_FRAMES) == 0) {
							stackMap = v + 8;
							stackMapSize = readInt(v + 2);
							frameCount = readUnsignedShort(v + 6);
							zip = false;
						}
						/*
						 * IMPORTANT! here we assume that the frames are
						 * ordered, as in the StackMapTable attribute, although
						 * this is not guaranteed by the attribute format.
						 */
					} else {
						for (k = 0; k < attrs.length; ++k) {
							if (attrs[k].type.equals(attrName)) {
								attr = attrs[k].read(this, v + 6,
										readInt(v + 2), c, codeStart - 8,
										labels);
								if (attr != null) {
									attr.next = cattrs;
									cattrs = attr;
								}
							}
						}
					}
					v += 6 + readInt(v + 2);
				}

				// 2nd phase: visits each instruction
				if (FRAMES && stackMap != 0) {
					// creates the very first (implicit) frame from the method
					// descriptor
					frameLocal = new Object[maxLocals];
					frameStack = new Object[maxStack];
					if (unzip) {
						int local = 0;
						if ((access & Opcodes.ACC_STATIC) == 0) {
							if ("<init>".equals(name)) {
								frameLocal[local++] = Opcodes.UNINITIALIZED_THIS;
							} else {
								frameLocal[local++] = readClass(header + 2, c);
							}
						}
						j = 1;
						loop: while (true) {
							k = j;
							switch (desc.charAt(j++)) {
							case 'Z':
							case 'C':
							case 'B':
							case 'S':
							case 'I':
								frameLocal[local++] = Opcodes.INTEGER;
								break;
							case 'F':
								frameLocal[local++] = Opcodes.FLOAT;
								break;
							case 'J':
								frameLocal[local++] = Opcodes.LONG;
								break;
							case 'D':
								frameLocal[local++] = Opcodes.DOUBLE;
								break;
							case '[':
								while (desc.charAt(j) == '[') {
									++j;
								}
								if (desc.charAt(j) == 'L') {
									++j;
									while (desc.charAt(j) != ';') {
										++j;
									}
								}
								frameLocal[local++] = desc.substring(k, ++j);
								break;
							case 'L':
								while (desc.charAt(j) != ';') {
									++j;
								}
								frameLocal[local++] = desc
								.substring(k + 1, j++);
								break;
							default:
								break loop;
							}
						}
						frameLocalCount = local;
					}
					/*
					 * for the first explicit frame the offset is not
					 * offset_delta + 1 but only offset_delta; setting the
					 * implicit frame offset to -1 allow the use of the
					 * "offset_delta + 1" rule in all cases
					 */
					frameOffset = -1;
					/*
					 * Finds labels for UNINITIALIZED frame types. Instead of
					 * decoding each element of the stack map table, we look for
					 * 3 consecutive bytes that "look like" an UNINITIALIZED
					 * type (tag 8, offset within code bounds, NEW instruction
					 * at this offset). We may find false positives (i.e. not
					 * real UNINITIALIZED types), but this should be rare, and
					 * the only consequence will be the creation of an unneeded
					 * label. This is better than creating a label for each NEW
					 * instruction, and faster than fully decoding the whole
					 * stack map table.
					 */
					for (j = stackMap; j < stackMap + stackMapSize - 2; ++j) {
						if (b[j] == 8) { // UNINITIALIZED FRAME TYPE
							k = readUnsignedShort(j + 1);
							if (k >= 0 && k < codeLength) { // potential offset
								if ((b[codeStart + k] & 0xFF) == Opcodes.NEW) { // NEW
									// at
									// this
									// offset
									readLabel(k, labels);
								}
							}
						}
					}
				}
				v = codeStart;
				Label l;
				while (v < codeEnd) {
					w = v - codeStart;

					l = labels[w];
					if (l != null) {
						mv.visitLabel(l);
						if (!skipDebug && l.line > 0) {
							mv.visitLineNumber(l.line, l);
						}
					}

					while (FRAMES && frameLocal != null
							&& (frameOffset == w || frameOffset == -1)) {
						// if there is a frame for this offset,
						// makes the visitor visit it,
						// and reads the next frame if there is one.
						if (!zip || unzip) {
							mv.visitFrame(Opcodes.F_NEW, frameLocalCount,
									frameLocal, frameStackCount, frameStack);
						} else if (frameOffset != -1) {
							mv.visitFrame(frameMode, frameLocalDiff,
									frameLocal, frameStackCount, frameStack);
						}

						if (frameCount > 0) {
							int tag, delta, n;
							if (zip) {
								tag = b[stackMap++] & 0xFF;
							} else {
								tag = MethodWriter.FULL_FRAME;
								frameOffset = -1;
							}
							frameLocalDiff = 0;
							if (tag < MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME) {
								delta = tag;
								frameMode = Opcodes.F_SAME;
								frameStackCount = 0;
							} else if (tag < MethodWriter.RESERVED) {
								delta = tag
								- MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME;
								stackMap = readFrameType(frameStack, 0,
										stackMap, c, labels);
								frameMode = Opcodes.F_SAME1;
								frameStackCount = 1;
							} else {
								delta = readUnsignedShort(stackMap);
								stackMap += 2;
								if (tag == MethodWriter.SAME_LOCALS_1_STACK_ITEM_FRAME_EXTENDED) {
									stackMap = readFrameType(frameStack, 0,
											stackMap, c, labels);
									frameMode = Opcodes.F_SAME1;
									frameStackCount = 1;
								} else if (tag >= MethodWriter.CHOP_FRAME
										&& tag < MethodWriter.SAME_FRAME_EXTENDED) {
									frameMode = Opcodes.F_CHOP;
									frameLocalDiff = MethodWriter.SAME_FRAME_EXTENDED
									- tag;
									frameLocalCount -= frameLocalDiff;
									frameStackCount = 0;
								} else if (tag == MethodWriter.SAME_FRAME_EXTENDED) {
									frameMode = Opcodes.F_SAME;
									frameStackCount = 0;
								} else if (tag < MethodWriter.FULL_FRAME) {
									j = unzip ? frameLocalCount : 0;
									for (k = tag
											- MethodWriter.SAME_FRAME_EXTENDED; k > 0; k--) {
										stackMap = readFrameType(frameLocal,
												j++, stackMap, c, labels);
									}
									frameMode = Opcodes.F_APPEND;
									frameLocalDiff = tag
									- MethodWriter.SAME_FRAME_EXTENDED;
									frameLocalCount += frameLocalDiff;
									frameStackCount = 0;
								} else { // if (tag == FULL_FRAME) {
									frameMode = Opcodes.F_FULL;
									n = frameLocalDiff = frameLocalCount = readUnsignedShort(stackMap);
									stackMap += 2;
									for (j = 0; n > 0; n--) {
										stackMap = readFrameType(frameLocal,
												j++, stackMap, c, labels);
									}
									n = frameStackCount = readUnsignedShort(stackMap);
									stackMap += 2;
									for (j = 0; n > 0; n--) {
										stackMap = readFrameType(frameStack,
												j++, stackMap, c, labels);
									}
								}
							}
							frameOffset += delta + 1;
							readLabel(frameOffset, labels);

							--frameCount;
						} else {
							frameLocal = null;
						}
					}

					int opcode = b[v] & 0xFF;
					switch (ClassWriter.TYPE[opcode]) {
					case ClassWriter.NOARG_INSN:
						mv.visitInsn(opcode);
						v += 1;
						break;
					case ClassWriter.IMPLVAR_INSN:
						if (opcode > Opcodes.ISTORE) {
							opcode -= 59; // ISTORE_0
							mv.visitVarInsn(Opcodes.ISTORE + (opcode >> 2),
									opcode & 0x3);
						} else {
							opcode -= 26; // ILOAD_0
							mv.visitVarInsn(Opcodes.ILOAD + (opcode >> 2),
									opcode & 0x3);
						}
						v += 1;
						break;
					case ClassWriter.LABEL_INSN:
						mv.visitJumpInsn(opcode, labels[w + readShort(v + 1)]);
						v += 3;
						break;
					case ClassWriter.LABELW_INSN:
						mv.visitJumpInsn(opcode - 33,
								labels[w + readInt(v + 1)]);
						v += 5;
						break;
					case ClassWriter.WIDE_INSN:
						opcode = b[v + 1] & 0xFF;
						if (opcode == Opcodes.IINC) {
							mv.visitIincInsn(readUnsignedShort(v + 2),
									readShort(v + 4));
							v += 6;
						} else {
							mv.visitVarInsn(opcode, readUnsignedShort(v + 2));
							v += 4;
						}
						break;
					case ClassWriter.TABL_INSN:
						// skips 0 to 3 padding bytes
						v = v + 4 - (w & 3);
						// reads instruction
						label = w + readInt(v);
						final int min = readInt(v + 4);
						final int max = readInt(v + 8);
						v += 12;
						final Label[] table = new Label[max - min + 1];
						for (j = 0; j < table.length; ++j) {
							table[j] = labels[w + readInt(v)];
							v += 4;
						}
						mv.visitTableSwitchInsn(min, max, labels[label], table);
						break;
					case ClassWriter.LOOK_INSN:
						// skips 0 to 3 padding bytes
						v = v + 4 - (w & 3);
						// reads instruction
						label = w + readInt(v);
						j = readInt(v + 4);
						v += 8;
						final int[] keys = new int[j];
						final Label[] values = new Label[j];
						for (j = 0; j < keys.length; ++j) {
							keys[j] = readInt(v);
							values[j] = labels[w + readInt(v + 4)];
							v += 8;
						}
						mv.visitLookupSwitchInsn(labels[label], keys, values);
						break;
					case ClassWriter.VAR_INSN:
						mv.visitVarInsn(opcode, b[v + 1] & 0xFF);
						v += 2;
						break;
					case ClassWriter.SBYTE_INSN:
						mv.visitIntInsn(opcode, b[v + 1]);
						v += 2;
						break;
					case ClassWriter.SHORT_INSN:
						mv.visitIntInsn(opcode, readShort(v + 1));
						v += 3;
						break;
					case ClassWriter.LDC_INSN:
						mv.visitLdcInsn(readConst(b[v + 1] & 0xFF, c));
						v += 2;
						break;
					case ClassWriter.LDCW_INSN:
						mv.visitLdcInsn(readConst(readUnsignedShort(v + 1), c));
						v += 3;
						break;
					case ClassWriter.FIELDORMETH_INSN:
					case ClassWriter.ITFDYNMETH_INSN:
						int cpIndex = items[readUnsignedShort(v + 1)];
						String iowner;
						// INVOKEDYNAMIC is receiverless
						if (opcode == Opcodes.INVOKEDYNAMIC) {
							iowner = Opcodes.INVOKEDYNAMIC_OWNER;
						} else {
							iowner = readClass(cpIndex, c);
							cpIndex = items[readUnsignedShort(cpIndex + 2)];
						}
						final String iname = readUTF8(cpIndex, c);
						final String idesc = readUTF8(cpIndex + 2, c);
						if (opcode < Opcodes.INVOKEVIRTUAL) {
							mv.visitFieldInsn(opcode, iowner, iname, idesc);
						} else {
							mv.visitMethodInsn(opcode, iowner, iname, idesc);
						}
						if (opcode == Opcodes.INVOKEINTERFACE
								|| opcode == Opcodes.INVOKEDYNAMIC) {
							v += 5;
						} else {
							v += 3;
						}
						break;
					case ClassWriter.TYPE_INSN:
						mv.visitTypeInsn(opcode, readClass(v + 1, c));
						v += 3;
						break;
					case ClassWriter.IINC_INSN:
						mv.visitIincInsn(b[v + 1] & 0xFF, b[v + 2]);
						v += 3;
						break;
						// case MANA_INSN:
					default:
						mv.visitMultiANewArrayInsn(readClass(v + 1, c),
								b[v + 3] & 0xFF);
						v += 4;
						break;
					}
				}
				l = labels[codeEnd - codeStart];
				if (l != null) {
					mv.visitLabel(l);
				}
				// visits the local variable tables
				if (!skipDebug && varTable != 0) {
					int[] typeTable = null;
					if (varTypeTable != 0) {
						k = readUnsignedShort(varTypeTable) * 3;
						w = varTypeTable + 2;
						typeTable = new int[k];
						while (k > 0) {
							typeTable[--k] = w + 6; // signature
							typeTable[--k] = readUnsignedShort(w + 8); // index
							typeTable[--k] = readUnsignedShort(w); // start
							w += 10;
						}
					}
					k = readUnsignedShort(varTable);
					w = varTable + 2;
					for (; k > 0; --k) {
						final int start = readUnsignedShort(w);
						final int length = readUnsignedShort(w + 2);
						final int index = readUnsignedShort(w + 8);
						String vsignature = null;
						if (typeTable != null) {
							for (int a = 0; a < typeTable.length; a += 3) {
								if (typeTable[a] == start
										&& typeTable[a + 1] == index) {
									vsignature = readUTF8(typeTable[a + 2], c);
									break;
								}
							}
						}
						mv.visitLocalVariable(readUTF8(w + 4, c),
								readUTF8(w + 6, c), vsignature, labels[start],
								labels[start + length], index);
						w += 10;
					}
				}
				// visits the other attributes
				while (cattrs != null) {
					attr = cattrs.next;
					cattrs.next = null;
					mv.visitAttribute(cattrs);
					cattrs = attr;
				}
				// visits the max stack and max locals values
				mv.visitMaxs(maxStack, maxLocals);
			}

			if (mv != null) {
				mv.visitEnd();
			}
		}

		// visits the end of the class
		classVisitor.visitEnd();
	}

	/**
	 * Makes the given visitor visit the Java class of this {@link ClassReader}.
	 * This class is the one specified in the constructor (see
	 * {@link #ClassReader(byte[]) ClassReader}).
	 * 
	 * @param classVisitor
	 *            the visitor that must visit this class.
	 * @param flags
	 *            option flags that can be used to modify the default behavior
	 *            of this class. See {@link #SKIP_DEBUG}, {@link #EXPAND_FRAMES}
	 *            , {@link #SKIP_FRAMES}, {@link #SKIP_CODE}.
	 */
	public void accept(final ClassVisitor classVisitor, final int flags) {
		accept(classVisitor, new Attribute[0], flags);
	}

	/**
	 * Copies the constant pool data into the given {@link ClassWriter}. Should
	 * be called before the {@link #accept(ClassVisitor, int)} method.
	 * 
	 * @param classWriter
	 *            the {@link ClassWriter} to copy constant pool into.
	 */
	void copyPool(final ClassWriter classWriter) {
		final char[] buf = new char[maxStringLength];
		final int ll = items.length;
		final Item[] items2 = new Item[ll];
		for (int i = 1; i < ll; i++) {
			int index = items[i];
			final int tag = b[index - 1];
			final Item item = new Item(i);
			int nameType;
			switch (tag) {
			case ClassWriter.FIELD:
			case ClassWriter.METH:
			case ClassWriter.IMETH:
				nameType = items[readUnsignedShort(index + 2)];
				item.set(tag, readClass(index, buf), readUTF8(nameType, buf),
						readUTF8(nameType + 2, buf));
				break;

			case ClassWriter.INT:
				item.set(readInt(index));
				break;

			case ClassWriter.FLOAT:
				item.set(Float.intBitsToFloat(readInt(index)));
				break;

			case ClassWriter.NAME_TYPE:
				item.set(tag, readUTF8(index, buf), readUTF8(index + 2, buf),
						null);
				break;

			case ClassWriter.LONG:
				item.set(readLong(index));
				++i;
				break;

			case ClassWriter.DOUBLE:
				item.set(Double.longBitsToDouble(readLong(index)));
				++i;
				break;

			case ClassWriter.UTF8: {
				String s = strings[i];
				if (s == null) {
					index = items[i];
					s = strings[i] = readUTF(index + 2,
							readUnsignedShort(index), buf);
				}
				item.set(tag, s, null, null);
			}
			break;

			// case ClassWriter.STR:
			// case ClassWriter.CLASS:
			default:
				item.set(tag, readUTF8(index, buf), null, null);
				break;
			}

			final int index2 = item.hashCode % items2.length;
			item.next = items2[index2];
			items2[index2] = item;
		}

		final int off = items[1] - 1;
		classWriter.pool.putByteArray(b, off, header - off);
		classWriter.items = items2;
		classWriter.threshold = (int) (0.75d * ll);
		classWriter.index = ll;
	}

	/**
	 * Returns the class's access flags (see {@link Opcodes}). This value may
	 * not reflect Deprecated and Synthetic flags when bytecode is before 1.5
	 * and those flags are represented by attributes.
	 * 
	 * @return the class access flags
	 * @see ClassVisitor#visit(int, int, String, String, String, String[])
	 */
	public int getAccess() {
		return readUnsignedShort(header);
	}

	/**
	 * Returns the internal name of the class (see
	 * {@link Type#getInternalName() getInternalName}).
	 * 
	 * @return the internal class name
	 * @see ClassVisitor#visit(int, int, String, String, String, String[])
	 */
	public String getClassName() {
		return readClass(header + 2, new char[maxStringLength]);
	}

	// ------------------------------------------------------------------------
	// Public methods
	// ------------------------------------------------------------------------

	/**
	 * Returns the internal names of the class's interfaces (see
	 * {@link Type#getInternalName() getInternalName}).
	 * 
	 * @return the array of internal names for all implemented interfaces or
	 *         <tt>null</tt>.
	 * @see ClassVisitor#visit(int, int, String, String, String, String[])
	 */
	public String[] getInterfaces() {
		int index = header + 6;
		final int n = readUnsignedShort(index);
		final String[] interfaces = new String[n];
		if (n > 0) {
			final char[] buf = new char[maxStringLength];
			for (int i = 0; i < n; ++i) {
				index += 2;
				interfaces[i] = readClass(index, buf);
			}
		}
		return interfaces;
	}

	/**
	 * Returns the start index of the constant pool item in {@link #b b}, plus
	 * one. <i>This method is intended for {@link Attribute} sub classes, and is
	 * normally not needed by class generators or adapters.</i>
	 * 
	 * @param item
	 *            the index a constant pool item.
	 * @return the start index of the constant pool item in {@link #b b}, plus
	 *         one.
	 */
	public int getItem(final int item) {
		return items[item];
	}

	/**
	 * Returns the internal of name of the super class (see
	 * {@link Type#getInternalName() getInternalName}). For interfaces, the
	 * super class is {@link Object}.
	 * 
	 * @return the internal name of super class, or <tt>null</tt> for
	 *         {@link Object} class.
	 * @see ClassVisitor#visit(int, int, String, String, String, String[])
	 */
	public String getSuperName() {
		final int n = items[readUnsignedShort(header + 4)];
		return n == 0 ? null : readUTF8(n, new char[maxStringLength]);
	}

	/**
	 * Reads a value of an annotation and makes the given visitor visit it.
	 * 
	 * @param v
	 *            the start offset in {@link #b b} of the value to be read
	 *            (<i>not including the value name constant pool index</i>).
	 * @param buf
	 *            buffer to be used to call {@link #readUTF8 readUTF8},
	 *            {@link #readClass(int, char[]) readClass} or
	 *            {@link #readConst readConst}.
	 * @param name
	 *            the name of the value to be read.
	 * @param av
	 *            the visitor that must visit the value.
	 * @return the end offset of the annotation value.
	 */
	private int readAnnotationValue(int v, final char[] buf, final String name,
			final AnnotationVisitor av) {
		int i;
		if (av == null) {
			switch (b[v] & 0xFF) {
			case 'e': // enum_const_value
				return v + 5;
			case '@': // annotation_value
				return readAnnotationValues(v + 3, buf, true, null);
			case '[': // array_value
				return readAnnotationValues(v + 1, buf, false, null);
			default:
				return v + 3;
			}
		}
		switch (b[v++] & 0xFF) {
		case 'I': // pointer to CONSTANT_Integer
		case 'J': // pointer to CONSTANT_Long
		case 'F': // pointer to CONSTANT_Float
		case 'D': // pointer to CONSTANT_Double
			av.visit(name, readConst(readUnsignedShort(v), buf));
			v += 2;
			break;
		case 'B': // pointer to CONSTANT_Byte
			av.visit(name,
					new Byte((byte) readInt(items[readUnsignedShort(v)])));
			v += 2;
			break;
		case 'Z': // pointer to CONSTANT_Boolean
			av.visit(name,
					readInt(items[readUnsignedShort(v)]) == 0 ? Boolean.FALSE
							: Boolean.TRUE);
			v += 2;
			break;
		case 'S': // pointer to CONSTANT_Short
			av.visit(name, new Short(
					(short) readInt(items[readUnsignedShort(v)])));
			v += 2;
			break;
		case 'C': // pointer to CONSTANT_Char
			av.visit(name, new Character(
					(char) readInt(items[readUnsignedShort(v)])));
			v += 2;
			break;
		case 's': // pointer to CONSTANT_Utf8
			av.visit(name, readUTF8(v, buf));
			v += 2;
			break;
		case 'e': // enum_const_value
			av.visitEnum(name, readUTF8(v, buf), readUTF8(v + 2, buf));
			v += 4;
			break;
		case 'c': // class_info
			av.visit(name, Type.getType(readUTF8(v, buf)));
			v += 2;
			break;
		case '@': // annotation_value
			v = readAnnotationValues(v + 2, buf, true,
					av.visitAnnotation(name, readUTF8(v, buf)));
			break;
		case '[': // array_value
			final int size = readUnsignedShort(v);
			v += 2;
			if (size == 0) {
				return readAnnotationValues(v - 2, buf, false,
						av.visitArray(name));
			}
			switch (b[v++] & 0xFF) {
			case 'B':
				final byte[] bv = new byte[size];
				for (i = 0; i < size; i++) {
					bv[i] = (byte) readInt(items[readUnsignedShort(v)]);
					v += 3;
				}
				av.visit(name, bv);
				--v;
				break;
			case 'Z':
				final boolean[] zv = new boolean[size];
				for (i = 0; i < size; i++) {
					zv[i] = readInt(items[readUnsignedShort(v)]) != 0;
					v += 3;
				}
				av.visit(name, zv);
				--v;
				break;
			case 'S':
				final short[] sv = new short[size];
				for (i = 0; i < size; i++) {
					sv[i] = (short) readInt(items[readUnsignedShort(v)]);
					v += 3;
				}
				av.visit(name, sv);
				--v;
				break;
			case 'C':
				final char[] cv = new char[size];
				for (i = 0; i < size; i++) {
					cv[i] = (char) readInt(items[readUnsignedShort(v)]);
					v += 3;
				}
				av.visit(name, cv);
				--v;
				break;
			case 'I':
				final int[] iv = new int[size];
				for (i = 0; i < size; i++) {
					iv[i] = readInt(items[readUnsignedShort(v)]);
					v += 3;
				}
				av.visit(name, iv);
				--v;
				break;
			case 'J':
				final long[] lv = new long[size];
				for (i = 0; i < size; i++) {
					lv[i] = readLong(items[readUnsignedShort(v)]);
					v += 3;
				}
				av.visit(name, lv);
				--v;
				break;
			case 'F':
				final float[] fv = new float[size];
				for (i = 0; i < size; i++) {
					fv[i] = Float
					.intBitsToFloat(readInt(items[readUnsignedShort(v)]));
					v += 3;
				}
				av.visit(name, fv);
				--v;
				break;
			case 'D':
				final double[] dv = new double[size];
				for (i = 0; i < size; i++) {
					dv[i] = Double
					.longBitsToDouble(readLong(items[readUnsignedShort(v)]));
					v += 3;
				}
				av.visit(name, dv);
				--v;
				break;
			default:
				v = readAnnotationValues(v - 3, buf, false, av.visitArray(name));
			}
		}
		return v;
	}

	/**
	 * Reads the values of an annotation and makes the given visitor visit them.
	 * 
	 * @param v
	 *            the start offset in {@link #b b} of the values to be read
	 *            (including the unsigned short that gives the number of
	 *            values).
	 * @param buf
	 *            buffer to be used to call {@link #readUTF8 readUTF8},
	 *            {@link #readClass(int, char[]) readClass} or
	 *            {@link #readConst readConst}.
	 * @param named
	 *            if the annotation values are named or not.
	 * @param av
	 *            the visitor that must visit the values.
	 * @return the end offset of the annotation values.
	 */
	private int readAnnotationValues(int v, final char[] buf,
			final boolean named, final AnnotationVisitor av) {
		int i = readUnsignedShort(v);
		v += 2;
		if (named) {
			for (; i > 0; --i) {
				v = readAnnotationValue(v + 2, buf, readUTF8(v, buf), av);
			}
		} else {
			for (; i > 0; --i) {
				v = readAnnotationValue(v, buf, null, av);
			}
		}
		if (av != null) {
			av.visitEnd();
		}
		return v;
	}

	/**
	 * Reads an attribute in {@link #b b}.
	 * 
	 * @param attrs
	 *            prototypes of the attributes that must be parsed during the
	 *            visit of the class. Any attribute whose type is not equal to
	 *            the type of one the prototypes is ignored (i.e. an empty
	 *            {@link Attribute} instance is returned).
	 * @param type
	 *            the type of the attribute.
	 * @param off
	 *            index of the first byte of the attribute's content in
	 *            {@link #b b}. The 6 attribute header bytes, containing the
	 *            type and the length of the attribute, are not taken into
	 *            account here (they have already been read).
	 * @param len
	 *            the length of the attribute's content.
	 * @param buf
	 *            buffer to be used to call {@link #readUTF8 readUTF8},
	 *            {@link #readClass(int, char[]) readClass} or
	 *            {@link #readConst readConst}.
	 * @param codeOff
	 *            index of the first byte of code's attribute content in
	 *            {@link #b b}, or -1 if the attribute to be read is not a code
	 *            attribute. The 6 attribute header bytes, containing the type
	 *            and the length of the attribute, are not taken into account
	 *            here.
	 * @param labels
	 *            the labels of the method's code, or <tt>null</tt> if the
	 *            attribute to be read is not a code attribute.
	 * @return the attribute that has been read, or <tt>null</tt> to skip this
	 *         attribute.
	 */
	private Attribute readAttribute(final Attribute[] attrs, final String type,
			final int off, final int len, final char[] buf, final int codeOff,
			final Label[] labels) {
		for (int i = 0; i < attrs.length; ++i) {
			if (attrs[i].type.equals(type)) {
				return attrs[i].read(this, off, len, buf, codeOff, labels);
			}
		}
		return new Attribute(type).read(this, off, len, null, -1, null);
	}

	/**
	 * Reads a byte value in {@link #b b}. <i>This method is intended for
	 * {@link Attribute} sub classes, and is normally not needed by class
	 * generators or adapters.</i>
	 * 
	 * @param index
	 *            the start index of the value to be read in {@link #b b}.
	 * @return the read value.
	 */
	public int readByte(final int index) {
		return b[index] & 0xFF;
	}

	/**
	 * Reads a class constant pool item in {@link #b b}. <i>This method is
	 * intended for {@link Attribute} sub classes, and is normally not needed by
	 * class generators or adapters.</i>
	 * 
	 * @param index
	 *            the start index of an unsigned short value in {@link #b b},
	 *            whose value is the index of a class constant pool item.
	 * @param buf
	 *            buffer to be used to read the item. This buffer must be
	 *            sufficiently large. It is not automatically resized.
	 * @return the String corresponding to the specified class item.
	 */
	public String readClass(final int index, final char[] buf) {
		// computes the start index of the CONSTANT_Class item in b
		// and reads the CONSTANT_Utf8 item designated by
		// the first two bytes of this CONSTANT_Class item
		return readUTF8(items[readUnsignedShort(index)], buf);
	}

	// ------------------------------------------------------------------------
	// Utility methods: low level parsing
	// ------------------------------------------------------------------------

	/**
	 * Reads a numeric or string constant pool item in {@link #b b}. <i>This
	 * method is intended for {@link Attribute} sub classes, and is normally not
	 * needed by class generators or adapters.</i>
	 * 
	 * @param item
	 *            the index of a constant pool item.
	 * @param buf
	 *            buffer to be used to read the item. This buffer must be
	 *            sufficiently large. It is not automatically resized.
	 * @return the {@link Integer}, {@link Float}, {@link Long}, {@link Double},
	 *         {@link String} or {@link Type} corresponding to the given
	 *         constant pool item.
	 */
	public Object readConst(final int item, final char[] buf) {
		final int index = items[item];
		switch (b[index - 1]) {
		case ClassWriter.INT:
			return new Integer(readInt(index));
		case ClassWriter.FLOAT:
			return new Float(Float.intBitsToFloat(readInt(index)));
		case ClassWriter.LONG:
			return new Long(readLong(index));
		case ClassWriter.DOUBLE:
			return new Double(Double.longBitsToDouble(readLong(index)));
		case ClassWriter.CLASS:
			return Type.getObjectType(readUTF8(index, buf));
			// case ClassWriter.STR:
		default:
			return readUTF8(index, buf);
		}
	}

	private int readFrameType(final Object[] frame, final int index, int v,
			final char[] buf, final Label[] labels) {
		final int type = b[v++] & 0xFF;
		switch (type) {
		case 0:
			frame[index] = Opcodes.TOP;
			break;
		case 1:
			frame[index] = Opcodes.INTEGER;
			break;
		case 2:
			frame[index] = Opcodes.FLOAT;
			break;
		case 3:
			frame[index] = Opcodes.DOUBLE;
			break;
		case 4:
			frame[index] = Opcodes.LONG;
			break;
		case 5:
			frame[index] = Opcodes.NULL;
			break;
		case 6:
			frame[index] = Opcodes.UNINITIALIZED_THIS;
			break;
		case 7: // Object
			frame[index] = readClass(v, buf);
			v += 2;
			break;
		default: // Uninitialized
			frame[index] = readLabel(readUnsignedShort(v), labels);
			v += 2;
		}
		return v;
	}

	/**
	 * Reads a signed int value in {@link #b b}. <i>This method is intended for
	 * {@link Attribute} sub classes, and is normally not needed by class
	 * generators or adapters.</i>
	 * 
	 * @param index
	 *            the start index of the value to be read in {@link #b b}.
	 * @return the read value.
	 */
	public int readInt(final int index) {
		final byte[] b = this.b;
		return (b[index] & 0xFF) << 24 | (b[index + 1] & 0xFF) << 16
		| (b[index + 2] & 0xFF) << 8 | b[index + 3] & 0xFF;
	}

	/**
	 * Returns the label corresponding to the given offset. The default
	 * implementation of this method creates a label for the given offset if it
	 * has not been already created.
	 * 
	 * @param offset
	 *            a bytecode offset in a method.
	 * @param labels
	 *            the already created labels, indexed by their offset. If a
	 *            label already exists for offset this method must not create a
	 *            new one. Otherwise it must store the new label in this array.
	 * @return a non null Label, which must be equal to labels[offset].
	 */
	protected Label readLabel(final int offset, final Label[] labels) {
		if (labels[offset] == null) {
			labels[offset] = new Label();
		}
		return labels[offset];
	}

	/**
	 * Reads a signed long value in {@link #b b}. <i>This method is intended for
	 * {@link Attribute} sub classes, and is normally not needed by class
	 * generators or adapters.</i>
	 * 
	 * @param index
	 *            the start index of the value to be read in {@link #b b}.
	 * @return the read value.
	 */
	public long readLong(final int index) {
		final long l1 = readInt(index);
		final long l0 = readInt(index + 4) & 0xFFFFFFFFL;
		return l1 << 32 | l0;
	}

	/**
	 * Reads parameter annotations and makes the given visitor visit them.
	 * 
	 * @param v
	 *            start offset in {@link #b b} of the annotations to be read.
	 * @param desc
	 *            the method descriptor.
	 * @param buf
	 *            buffer to be used to call {@link #readUTF8 readUTF8},
	 *            {@link #readClass(int, char[]) readClass} or
	 *            {@link #readConst readConst}.
	 * @param visible
	 *            <tt>true</tt> if the annotations to be read are visible at
	 *            runtime.
	 * @param mv
	 *            the visitor that must visit the annotations.
	 */
	private void readParameterAnnotations(int v, final String desc,
			final char[] buf, final boolean visible, final MethodVisitor mv) {
		int i;
		final int n = b[v++] & 0xFF;
		// workaround for a bug in javac (javac compiler generates a parameter
		// annotation array whose size is equal to the number of parameters in
		// the Java source file, while it should generate an array whose size is
		// equal to the number of parameters in the method descriptor - which
		// includes the synthetic parameters added by the compiler). This work-
		// around supposes that the synthetic parameters are the first ones.
		final int synthetics = Type.getArgumentTypes(desc).length - n;
		AnnotationVisitor av;
		for (i = 0; i < synthetics; ++i) {
			// virtual annotation to detect synthetic parameters in MethodWriter
			av = mv.visitParameterAnnotation(i, "Ljava/lang/Synthetic;", false);
			if (av != null) {
				av.visitEnd();
			}
		}
		for (; i < n + synthetics; ++i) {
			int j = readUnsignedShort(v);
			v += 2;
			for (; j > 0; --j) {
				av = mv.visitParameterAnnotation(i, readUTF8(v, buf), visible);
				v = readAnnotationValues(v + 2, buf, true, av);
			}
		}
	}

	/**
	 * Reads a signed short value in {@link #b b}. <i>This method is intended
	 * for {@link Attribute} sub classes, and is normally not needed by class
	 * generators or adapters.</i>
	 * 
	 * @param index
	 *            the start index of the value to be read in {@link #b b}.
	 * @return the read value.
	 */
	public short readShort(final int index) {
		final byte[] b = this.b;
		return (short) ((b[index] & 0xFF) << 8 | b[index + 1] & 0xFF);
	}

	/**
	 * Reads an unsigned short value in {@link #b b}. <i>This method is intended
	 * for {@link Attribute} sub classes, and is normally not needed by class
	 * generators or adapters.</i>
	 * 
	 * @param index
	 *            the start index of the value to be read in {@link #b b}.
	 * @return the read value.
	 */
	public int readUnsignedShort(final int index) {
		final byte[] b = this.b;
		return (b[index] & 0xFF) << 8 | b[index + 1] & 0xFF;
	}

	/**
	 * Reads UTF8 string in {@link #b b}.
	 * 
	 * @param index
	 *            start offset of the UTF8 string to be read.
	 * @param utfLen
	 *            length of the UTF8 string to be read.
	 * @param buf
	 *            buffer to be used to read the string. This buffer must be
	 *            sufficiently large. It is not automatically resized.
	 * @return the String corresponding to the specified UTF8 string.
	 */
	private String readUTF(int index, final int utfLen, final char[] buf) {
		final int endIndex = index + utfLen;
		final byte[] b = this.b;
		int strLen = 0;
		int c;
		int st = 0;
		char cc = 0;
		while (index < endIndex) {
			c = b[index++];
			switch (st) {
			case 0:
				c = c & 0xFF;
				if (c < 0x80) { // 0xxxxxxx
					buf[strLen++] = (char) c;
				} else if (c < 0xE0 && c > 0xBF) { // 110x xxxx 10xx xxxx
					cc = (char) (c & 0x1F);
					st = 1;
				} else { // 1110 xxxx 10xx xxxx 10xx xxxx
					cc = (char) (c & 0x0F);
					st = 2;
				}
				break;

			case 1: // byte 2 of 2-byte char or byte 3 of 3-byte char
				buf[strLen++] = (char) (cc << 6 | c & 0x3F);
				st = 0;
				break;

			case 2: // byte 2 of 3-byte char
				cc = (char) (cc << 6 | c & 0x3F);
				st = 1;
				break;
			}
		}
		return new String(buf, 0, strLen);
	}

	/**
	 * Reads an UTF8 string constant pool item in {@link #b b}. <i>This method
	 * is intended for {@link Attribute} sub classes, and is normally not needed
	 * by class generators or adapters.</i>
	 * 
	 * @param index
	 *            the start index of an unsigned short value in {@link #b b},
	 *            whose value is the index of an UTF8 constant pool item.
	 * @param buf
	 *            buffer to be used to read the item. This buffer must be
	 *            sufficiently large. It is not automatically resized.
	 * @return the String corresponding to the specified UTF8 item.
	 */
	public String readUTF8(int index, final char[] buf) {
		final int item = readUnsignedShort(index);
		final String s = strings[item];
		if (s != null) {
			return s;
		}
		index = items[item];
		return strings[item] = readUTF(index + 2, readUnsignedShort(index), buf);
	}
}
